package com.quectel.core.module.scheduling.service.impl;


import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.base.Preconditions;
import com.quectel.constant.core.scheduling.*;
import com.quectel.constant.global.SqlConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.util.aspect.lock.annotation.Lock;
import com.quectel.core.constants.RedisCacheConstants;
import com.quectel.core.constants.RedisLockConstants;
import com.quectel.core.module.dept.dto.DeptPersonDto;
import com.quectel.core.module.dept.service.DeptPersonService;
import com.quectel.core.module.scheduling.dao.SchedulingAttendanceDailyDao;
import com.quectel.core.module.scheduling.dto.*;
import com.quectel.core.module.scheduling.dto.mongo.MongoOriginalCheckRecordDto;
import com.quectel.core.module.scheduling.dto.vo.SchedulingOvertimeCount;
import com.quectel.core.module.scheduling.entity.SchedulingAttendanceDailyEntity;
import com.quectel.core.module.scheduling.entity.mongo.MongoOriginalCheckRecordModel;
import com.quectel.core.module.scheduling.service.*;
import com.quectel.core.module.scheduling.service.mongo.MongoOriginalCheckRecordService;
import com.quectel.core.module.schedulingapply.dto.SchedulingApplyLeaveRecordDto;
import com.quectel.core.module.schedulingapply.dto.SchedulingApplyOutRecordDto;
import com.quectel.core.module.schedulingapply.dto.SchedulingApplyRenewCardRecordDto;
import com.quectel.core.module.schedulingapply.dto.SchedulingApplyTripRecordDto;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingBalanceTimeVO;
import com.quectel.core.module.schedulingapply.dto.vo.SchedulingTimeVO;
import com.quectel.core.module.schedulingapply.service.SchedulingApplyLeaveRecordService;
import com.quectel.core.module.schedulingapply.service.SchedulingApplyOutRecordService;
import com.quectel.core.module.schedulingapply.service.SchedulingApplyRenewCardRecordService;
import com.quectel.core.module.schedulingapply.service.SchedulingApplyTripRecordService;
import com.quectel.core.module.village.dto.VillageDto;
import com.quectel.core.module.village.service.VillageService;
import com.quectel.util.common.CopyUtils;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.kit.CacheKit;
import com.quectel.util.kit.Snowflake;
import com.quectel.util.mybatis.MyBatisPlusUtils;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.LongFunction;
import java.util.stream.Collectors;

/**
 * @author ryan
 * @email ryan.ran@quectel.com
 * @date 2023/07/13 18:58
 */
@DubboService
public class SchedulingAttendanceDailyServiceImpl implements SchedulingAttendanceDailyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingAttendanceDailyServiceImpl.class);

    /**
     * 入参解释:实体id
     */
    private static final LongFunction<String> SCHEDULING_ATTENDANCE_DAILY_CACHE_BY_ID_FUNC = id -> String.format(RedisCacheConstants.PROJECT_NAME + "SCHEDULING_ATTENDANCE_DAILY_CACHE_BY_ID:%d", id);

    /**
     * 清除缓存方法
     */
    private static final Consumer<SchedulingAttendanceDailyDto> CLEAR_CACHE_FUNC = dto -> {
        CacheKit.invalidRedisCache(SCHEDULING_ATTENDANCE_DAILY_CACHE_BY_ID_FUNC.apply(dto.getId()));
    };

    // 记录的最小时间精度，0.01小时
    private static final long CAL_TINE_ACCURACY_MILLISECOND = 36 * 1000L;

    @Autowired
    private SchedulingAttendanceDailyDao schedulingAttendanceDailyDao;
    @Autowired
    private SchedulingAttendanceGroupPersonBindingService schedulingAttendanceGroupPersonBindingService;
    @Autowired
    private SchedulingAttendanceGroupPersonWorkTimeConfigService schedulingAttendanceGroupPersonWorkTimeConfigService;
    @Autowired
    private SchedulingAttendanceDailyTimeService schedulingAttendanceDailyTimeService;
    @Autowired
    private MongoOriginalCheckRecordService mongoOriginalCheckRecordService;
    @Autowired
    private DeptPersonService deptPersonService;
    @Autowired
    private SchedulingAttendanceGroupService schedulingAttendanceGroupService;
    @Autowired
    private VillageService villageService;
    @Autowired
    private SchedulingCalendarConfigBindingService schedulingCalendarConfigBindingService;
    @Autowired
    private SchedulingAttendanceDailyApplyService schedulingAttendanceDailyApplyService;
    @Autowired
    private SchedulingApplyLeaveRecordService schedulingApplyLeaveRecordService;
    @Autowired
    private SchedulingApplyOutRecordService schedulingApplyOutRecordService;
    @Autowired
    private SchedulingApplyTripRecordService schedulingApplyTripRecordService;
    @Autowired
    private SchedulingApplyRenewCardRecordService schedulingApplyRenewCardRecordService;
    @Autowired
    private SchedulingLeaveBalanceService schedulingLeaveBalanceService;
    @Autowired
    private SchedulingWorkOvertimeConfigService schedulingWorkOvertimeConfigService;


    @Override
    public SchedulingOvertimeCount countOvertime(Map<String, Object> params) {
        return schedulingAttendanceDailyDao.countOvertime(params);
    }

    /**
     * 处理请假余额
     *
     * @param personId      人员id
     * @param dailyId       对应的考勤记录id
     * @param recordId      申请成功后的记录id
     * @param num           回收的数量
     * @param unit          回收的单位 这里是小时
     * @param calCursorDate 新的游标时间
     */
    private void handleLeaveBalance(Long personId, Long dailyId, Long recordId, BigDecimal num, Byte unit, Date calCursorDate) {
        //查询余额详情 首先按顺序回收,回收不了则下一个
        SchedulingApplyLeaveRecordDto leaveRecordDto = schedulingApplyLeaveRecordService.selectById(recordId);
        if (leaveRecordDto == null) {
            return;
        }
        SchedulingTimeVO balanceDetail = leaveRecordDto.getBalanceDetail();
        if (balanceDetail == null || CollectionUtils.isEmpty(balanceDetail.getBalanceTimes())) {
            return;
        }
        //换算单位
        BigDecimal changedNum = SchedulingLeaveConstants.TimeUnitEnum.changeUnit(num, unit, leaveRecordDto.getTimeUnit());
        List<SchedulingBalanceTimeVO> balanceTimes = balanceDetail.getBalanceTimes();
        for (SchedulingBalanceTimeVO balanceTime : balanceTimes) {
            //如果回收的余额小于申请余额 则一次性回收
            if (balanceTime.getAppliedBalance().compareTo(changedNum) > 0) {
                BigDecimal appliedBalanceMills = balanceTime.getAppliedBalance();
                balanceTime.setAppliedBalance(appliedBalanceMills.subtract(changedNum));
                //回收余额
                schedulingLeaveBalanceService.updateBalance(
                        personId,
                        SchedulingApplyConstants.BalanceTypeEnum.DAILY_CAL_UPDATE.getType(),
                        dailyId,
                        balanceTime.getBalanceId(),
                        appliedBalanceMills,
                        leaveRecordDto.getTimeUnit()
                );
            } else {
                changedNum = changedNum.subtract(balanceTime.getAppliedBalance());
                balanceTime.setAppliedBalance(BigDecimal.ZERO);
                //回收余额
                schedulingLeaveBalanceService.updateBalance(
                        personId,
                        SchedulingApplyConstants.BalanceTypeEnum.DAILY_CAL_UPDATE.getType(),
                        dailyId,
                        balanceTime.getBalanceId(),
                        balanceTime.getAppliedBalance(),
                        leaveRecordDto.getTimeUnit());
            }
        }
        //更新余额
        BigDecimal timeLength = leaveRecordDto.getTimeLength();
        BigDecimal changedTimeLength = SchedulingLeaveConstants.TimeUnitEnum.changeUnit(num, unit, leaveRecordDto.getTimeUnit());
        SchedulingApplyLeaveRecordDto newDto = new SchedulingApplyLeaveRecordDto();
        newDto.setId(leaveRecordDto.getId());
        newDto.setCalCursorTime(calCursorDate);
        newDto.setTimeLength(timeLength.subtract(changedTimeLength));
        newDto.setBalanceDetailJson(JacksonUtils.toJsonString(balanceTimes));
        schedulingApplyLeaveRecordService.updateById(newDto);

    }


    /**
     * 根据 checkDate 找打正确的考勤记录，可能是多个
     * 并不能selectOne(dailyCheckReqDto.getPersonId(), checkDate) 直接拿打卡当天的，应该根据考勤时间段找到
     * 未找到会自动生成打卡当天的，不会返回 null
     *
     * @param personId
     * @param checkDate
     * @return
     */
    private List<SchedulingAttendanceDailyDto> seekDaily(Long personId, Date checkDate) {
        List<SchedulingAttendanceDailyDto> attendanceDailyList = new ArrayList<>();
        String dateStr = DateUtils.format(checkDate, DateUtils.FormatType.SIMPLE);
        // 优先找到打卡当天
        SchedulingAttendanceDailyDto todayAttendanceDailyDto = selectOne(personId, dateStr);
        if (Objects.nonNull(todayAttendanceDailyDto) && CollectionUtils.isNotEmpty(todayAttendanceDailyDto.getTimes())) {
            List<Date> dateList = new ArrayList<>();
            for (SchedulingAttendanceDailyTimeDto time : todayAttendanceDailyDto.getTimes()) {
                dateList.add(time.getStartSignTime());
                dateList.add(time.getEndSignTime());
            }
            Date[] minMaxDatesHelper = findMinMaxDatesHelper(dateList, 0, dateList.size() - 1);
            if (DateUtil.compare(minMaxDatesHelper[0], checkDate) < 0 && DateUtil.compare(minMaxDatesHelper[1], checkDate) > 0) {
                attendanceDailyList.add(todayAttendanceDailyDto);
            }
        }
        // 昨天
        String yesterday = DateUtils.format(DateUtil.offsetDay(checkDate, -1), DateUtils.FormatType.SIMPLE);
        SchedulingAttendanceDailyDto yesterdayAttendanceDailyDto = selectOne(personId, yesterday);
        if (Objects.nonNull(yesterdayAttendanceDailyDto) && CollectionUtils.isNotEmpty(yesterdayAttendanceDailyDto.getTimes())) {
            List<Date> dateList = new ArrayList<>();
            for (SchedulingAttendanceDailyTimeDto time : yesterdayAttendanceDailyDto.getTimes()) {
                dateList.add(time.getStartSignTime());
                dateList.add(time.getEndSignTime());
            }
            Date[] minMaxDatesHelper = findMinMaxDatesHelper(dateList, 0, dateList.size() - 1);
            if (DateUtil.compare(minMaxDatesHelper[0], checkDate) < 0 && DateUtil.compare(minMaxDatesHelper[1], checkDate) > 0) {
                attendanceDailyList.add(yesterdayAttendanceDailyDto);
            }
        }
        // 明天
        String tomorrow = DateUtils.format(DateUtil.offsetDay(checkDate, 1), DateUtils.FormatType.SIMPLE);
        SchedulingAttendanceDailyDto tomorrowAttendanceDailyDto = selectOne(personId, tomorrow);
        if (Objects.nonNull(tomorrowAttendanceDailyDto) && CollectionUtils.isNotEmpty(tomorrowAttendanceDailyDto.getTimes())) {
            List<Date> dateList = new ArrayList<>();
            for (SchedulingAttendanceDailyTimeDto time : tomorrowAttendanceDailyDto.getTimes()) {
                dateList.add(time.getStartSignTime());
                dateList.add(time.getEndSignTime());
            }
            Date[] minMaxDatesHelper = findMinMaxDatesHelper(dateList, 0, dateList.size() - 1);
            if (DateUtil.compare(minMaxDatesHelper[0], checkDate) < 0 && DateUtil.compare(minMaxDatesHelper[1], checkDate) > 0) {
                attendanceDailyList.add(tomorrowAttendanceDailyDto);
            }
        }

        if (CollectionUtils.isNotEmpty(attendanceDailyList)) {
            return attendanceDailyList;
        }

        // 方法执行到这里，说明考勤记录未生成，或者未配置时间段
        AttendanceDailyGenReq attendanceDailyGenReq = new AttendanceDailyGenReq();
        attendanceDailyGenReq.setPersonId(personId);
        attendanceDailyGenReq.setDataStr(dateStr);
        attendanceDailyList.add(todayAttendanceDailyDto == null ? gen(attendanceDailyGenReq) : todayAttendanceDailyDto);
        return attendanceDailyList;
    }

    private static Date[] findMinMaxDatesHelper(List<Date> dateList, int start, int end) {
        if (start == end) {
            Date date = dateList.get(start);
            return new Date[]{date, date};
        }

        if (start + 1 == end) {
            Date date1 = dateList.get(start);
            Date date2 = dateList.get(end);
            return date1.before(date2) ? new Date[]{date1, date2} : new Date[]{date2, date1};
        }

        int mid = start + (end - start) / 2;
        Date[] leftMinMax = findMinMaxDatesHelper(dateList, start, mid);
        Date[] rightMinMax = findMinMaxDatesHelper(dateList, mid + 1, end);

        Date minDate = leftMinMax[0].before(rightMinMax[0]) ? leftMinMax[0] : rightMinMax[0];
        Date maxDate = leftMinMax[1].after(rightMinMax[1]) ? leftMinMax[1] : rightMinMax[1];

        return new Date[]{minDate, maxDate};
    }

    /**
     * 申请撤销，找到申请影响过的考勤，重新打卡
     *
     * @param revokeReplayReqDto
     * @return
     */
    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.DAILY_CHECK_LOCK + "#{#revokeReplayReqDto.personId}"
            }
    )
    @Transactional
    public Boolean revokeReplay(RevokeReplayReqDto revokeReplayReqDto) {
        Map<String, Object> queryMap = new HashMap<>();
        queryMap.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyApplyDto::getApplyId), revokeReplayReqDto.getApplyId());
        queryMap.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyApplyDto::getApplyType), revokeReplayReqDto.getApplyType());
        List<SchedulingAttendanceDailyApplyDto> applyDtos = schedulingAttendanceDailyApplyService.queryList(queryMap);

        for (SchedulingAttendanceDailyApplyDto applyDto : applyDtos) {
            Long dailyId = applyDto.getDailyId();
            SchedulingAttendanceDailyDto attendanceDailyDto = selectCacheById(dailyId);
            if (Objects.nonNull(attendanceDailyDto)) {
                deleteById(attendanceDailyDto.getId());
                AttendanceDailyGenReq genReq = new AttendanceDailyGenReq();
                genReq.setPersonId(attendanceDailyDto.getPersonId());
                genReq.setDataStr(DateUtils.format(attendanceDailyDto.getDate(), DateUtils.FormatType.SIMPLE));
                SchedulingAttendanceDailyDto gen = gen(genReq);

                Map<String, Object> queryCheckRecordMap = new HashMap<>();
                queryCheckRecordMap.put(LambdaUtil.getFieldName(MongoOriginalCheckRecordModel::getDailyId), attendanceDailyDto.getId());
                List<MongoOriginalCheckRecordDto> checkRecordDtos = mongoOriginalCheckRecordService.queryList(queryCheckRecordMap);

                for (MongoOriginalCheckRecordDto checkRecordDto : checkRecordDtos) {
                    DailyCheckReqDto checkReqDto = new DailyCheckReqDto();
                    checkReqDto.setSchedulingAttendanceDaily(gen);
                    checkReqDto.setPersonId(attendanceDailyDto.getPersonId());
                    checkReqDto.setCheckDate(checkRecordDto.getCheckTime());
                    checkReqDto.setIsField(checkRecordDto.getIsField());
                    checkReqDto.setIsRepair(checkRecordDto.getIsRepair());
                    checkReqDto.setLongitude(checkRecordDto.getLongitude());
                    checkReqDto.setLatitude(checkRecordDto.getLatitude());
                    checkReqDto.setAddress(checkRecordDto.getAddress());
                    checkReqDto.setPics(JacksonUtils.toJsonString(checkRecordDto.getPics()));
                    checkReqDto.setRemark(checkRecordDto.getRemark());
                    check(checkReqDto);
                }
            }
        }
        return Boolean.TRUE;
    }


    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.DAILY_CHECK_LOCK + "#{#applyDto.personId}"
            }
    )
    @Transactional
    public void applyEffect(SchedulingAttendanceDailyApplyDto applyDto) {
        final Date currentDate = new Date();
        if (SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType().equals(applyDto.getApplyType())) {
            // 请假申请
            SchedulingApplyLeaveRecordDto schedulingApplyLeaveRecordDto = schedulingApplyLeaveRecordService.selectById(applyDto.getApplyId());
            if (Objects.nonNull(schedulingApplyLeaveRecordDto) && SchedulingApplyConstants.APPROVED_STATUS.equals(schedulingApplyLeaveRecordDto.getStatus())
                    && DateUtil.offsetHour(currentDate, 1).getTime() > DateUtil.beginOfDay(schedulingApplyLeaveRecordDto.getStartTime()).getTime()) {
                Map<String, Object> params = new HashMap<>();
                params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getPersonId), applyDto.getPersonId());
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStartTime), DateUtils.format(schedulingApplyLeaveRecordDto.getStartTime(), DateUtils.FormatType.COMMON));
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getEndTime), DateUtils.format(schedulingApplyLeaveRecordDto.getEndTime(), DateUtils.FormatType.COMMON));
                List<SchedulingAttendanceDailyTimeDto> dailyTimeDtos = schedulingAttendanceDailyTimeService.queryList(params);
                Map<Long, List<SchedulingAttendanceDailyTimeDto>> collect = dailyTimeDtos.parallelStream()
                        .collect(Collectors.groupingBy(SchedulingAttendanceDailyTimeDto::getAttendanceDailyId));

                // 请假有效时间
                BigDecimal recordIncrementLeaveHour = BigDecimal.ZERO;

                // 需要计算此次申请，花费总时间
                BigDecimal allSpendHour = BigDecimal.ZERO;

                // 计算游标时间
                Date calCursorDate = schedulingApplyLeaveRecordDto.getCalCursorTime() == null ? schedulingApplyLeaveRecordDto.getStartTime() : schedulingApplyLeaveRecordDto.getCalCursorTime();

                if (CollectionUtils.isNotEmpty(dailyTimeDtos)) {
                    Date maxClosingTime = null;
                    for (SchedulingAttendanceDailyTimeDto dailyTimeDto : dailyTimeDtos) {
                        if (maxClosingTime == null || DateUtil.compare(dailyTimeDto.getClosingTime(), maxClosingTime) > 0) {
                            maxClosingTime = dailyTimeDto.getClosingTime();
                        }
                    }
                    if (maxClosingTime != null) {
                        calCursorDate = new Date(Math.min(schedulingApplyLeaveRecordDto.getEndTime().getTime(), maxClosingTime.getTime()));
                        allSpendHour = new BigDecimal(String.valueOf(calCursorDate.getTime() - schedulingApplyLeaveRecordDto.getStartTime().getTime()))
                                .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                    }
                }

                for (Map.Entry<Long, List<SchedulingAttendanceDailyTimeDto>> longListEntry : collect.entrySet()) {
                    SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                    newlyDailyApplyDto.setApplyId(applyDto.getApplyId());
                    newlyDailyApplyDto.setDailyId(longListEntry.getKey());
                    newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType());
                    newlyDailyApplyDto.setPersonId(applyDto.getPersonId());
                    schedulingAttendanceDailyApplyService.save(newlyDailyApplyDto);

                    BigDecimal incrementLeaveHour = BigDecimal.ZERO;
                    for (SchedulingAttendanceDailyTimeDto time : longListEntry.getValue()) {
                        if (schedulingApplyLeaveRecordDto.getEndTime().getTime() > time.getWorkTime().getTime()
                                && schedulingApplyLeaveRecordDto.getStartTime().getTime() < time.getClosingTime().getTime()) {
                            Date intersectionStartTime = new Date(Math.max(schedulingApplyLeaveRecordDto.getStartTime().getTime(), time.getWorkTime().getTime()));
                            Date intersectionEndTime = new Date(Math.min(schedulingApplyLeaveRecordDto.getEndTime().getTime(), time.getClosingTime().getTime()));
                            if (intersectionEndTime.getTime() - intersectionStartTime.getTime() > CAL_TINE_ACCURACY_MILLISECOND) {
                                // 存在交集，并且交集范围大于记录的最小精度
                                BigDecimal increment = new BigDecimal(String.valueOf(intersectionEndTime.getTime() - intersectionStartTime.getTime()))
                                        .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                                incrementLeaveHour = incrementLeaveHour.add(increment);
                            }

                            // 请假造成的影响，更新打卡时间段状态
                            SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
                            updateTimeDto.setId(time.getId());
                            if (schedulingApplyLeaveRecordDto.getEndTime().getTime() >= time.getClosingTime().getTime()) {
                                updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                            }
                            if (schedulingApplyLeaveRecordDto.getStartTime().getTime() <= time.getWorkTime().getTime()) {
                                updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                            }
                            schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
                        }
                    }

                    SchedulingAttendanceDailyDto attendanceDailyDto = selectCacheById(longListEntry.getKey());
                    if (BigDecimal.ZERO.compareTo(incrementLeaveHour) < 0) {
                        BigDecimal nowLeaveHour = Objects.isNull(attendanceDailyDto.getLeaveHour()) ? BigDecimal.ZERO : attendanceDailyDto.getLeaveHour();
                        SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
                        updateDailyDto.setId(attendanceDailyDto.getId());
                        updateDailyDto.setLeaveHour(nowLeaveHour.add(incrementLeaveHour));
                        updateById(updateDailyDto);
                    }
                    recordIncrementLeaveHour = recordIncrementLeaveHour.add(incrementLeaveHour);

                    // 检查申请后，时间段的打卡状态是否正常
                    // 要重新根据每日考勤记录 id 拿时间段，主要是变更每日考勤的总状态，如果状态是正常的，就不变更了
                    if (SchedulingAttendanceDailyConstants.CheckStatusEnum.ABNORMAL.getStatus().equals(attendanceDailyDto.getCheckStatus())) {
                        List<SchedulingAttendanceDailyTimeDto> dailyAllTimes = schedulingAttendanceDailyTimeService.selectByDailyId(attendanceDailyDto.getId());
                        boolean b = dailyAllTimes.parallelStream()
                                .anyMatch(o -> !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckWorkStatus())
                                        || !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckClosingStatus()));
                        if (!b) {
                            SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
                            updateDailyDto.setId(attendanceDailyDto.getId());
                            updateDailyDto.setCheckStatus(SchedulingAttendanceDailyConstants.CheckStatusEnum.NORMAL.getStatus());
                            updateById(updateDailyDto);
                        }
                    }

                }

                // 其他操作
                handleLeaveBalance(applyDto.getPersonId(), applyDto.getDailyId(), applyDto.getApplyId(), allSpendHour.subtract(recordIncrementLeaveHour), SchedulingLeaveConstants.TimeUnitEnum.HOUR.getUnit(), calCursorDate);

            }
        } else if (SchedulingApplyConstants.ApplyTypeEnum.OUT.getType().equals(applyDto.getApplyType())) {
            // 外出申请
            SchedulingApplyOutRecordDto schedulingApplyOutRecordDto = schedulingApplyOutRecordService.selectById(applyDto.getApplyId());
            if (Objects.nonNull(schedulingApplyOutRecordDto) && SchedulingApplyConstants.APPROVED_STATUS.equals(schedulingApplyOutRecordDto.getStatus())
                    && DateUtil.offsetHour(currentDate, 1).getTime() > DateUtil.beginOfDay(schedulingApplyOutRecordDto.getStartTime()).getTime()) {
                Map<String, Object> params = new HashMap<>();
                params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getPersonId), applyDto.getPersonId());
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStartTime), DateUtils.format(schedulingApplyOutRecordDto.getStartTime(), DateUtils.FormatType.COMMON));
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getEndTime), DateUtils.format(schedulingApplyOutRecordDto.getEndTime(), DateUtils.FormatType.COMMON));
                List<SchedulingAttendanceDailyTimeDto> dailyTimeDtos = schedulingAttendanceDailyTimeService.queryList(params);
                Map<Long, List<SchedulingAttendanceDailyTimeDto>> collect = dailyTimeDtos.parallelStream()
                        .collect(Collectors.groupingBy(SchedulingAttendanceDailyTimeDto::getAttendanceDailyId));

                for (Map.Entry<Long, List<SchedulingAttendanceDailyTimeDto>> longListEntry : collect.entrySet()) {
                    SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                    newlyDailyApplyDto.setApplyId(applyDto.getApplyId());
                    newlyDailyApplyDto.setDailyId(longListEntry.getKey());
                    newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.OUT.getType());
                    newlyDailyApplyDto.setPersonId(applyDto.getPersonId());
                    schedulingAttendanceDailyApplyService.save(newlyDailyApplyDto);

                    BigDecimal incrementOutHour = BigDecimal.ZERO;
                    for (SchedulingAttendanceDailyTimeDto time : longListEntry.getValue()) {
                        if (schedulingApplyOutRecordDto.getEndTime().getTime() > time.getWorkTime().getTime()
                                && schedulingApplyOutRecordDto.getStartTime().getTime() < time.getClosingTime().getTime()) {
                            Date intersectionStartTime = new Date(Math.max(schedulingApplyOutRecordDto.getStartTime().getTime(), time.getWorkTime().getTime()));
                            Date intersectionEndTime = new Date(Math.min(schedulingApplyOutRecordDto.getEndTime().getTime(), time.getClosingTime().getTime()));
                            if (intersectionEndTime.getTime() - intersectionStartTime.getTime() > CAL_TINE_ACCURACY_MILLISECOND) {
                                // 存在交集，并且交集范围大于记录的最小精度
                                BigDecimal increment = new BigDecimal(String.valueOf(intersectionEndTime.getTime() - intersectionStartTime.getTime()))
                                        .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                                incrementOutHour = incrementOutHour.add(increment);
                            }

                            // 外出造成的影响，更新打卡时间段状态
                            SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
                            updateTimeDto.setId(time.getId());
                            if (schedulingApplyOutRecordDto.getEndTime().getTime() >= time.getClosingTime().getTime()) {
                                updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                            }
                            if (schedulingApplyOutRecordDto.getStartTime().getTime() <= time.getWorkTime().getTime()) {
                                updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                            }
                            schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
                        }
                    }

                    SchedulingAttendanceDailyDto attendanceDailyDto = selectCacheById(longListEntry.getKey());

                    if (BigDecimal.ZERO.compareTo(incrementOutHour) < 0) {
                        BigDecimal nowOutHour = Objects.isNull(attendanceDailyDto.getOutHour()) ? BigDecimal.ZERO : attendanceDailyDto.getOutHour();
                        SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
                        updateDailyDto.setId(attendanceDailyDto.getId());
                        updateDailyDto.setOutHour(nowOutHour.add(incrementOutHour));
                        updateById(updateDailyDto);
                    }

                    if (SchedulingAttendanceDailyConstants.CheckStatusEnum.ABNORMAL.getStatus().equals(attendanceDailyDto.getCheckStatus())) {
                        List<SchedulingAttendanceDailyTimeDto> dailyAllTimes = schedulingAttendanceDailyTimeService.selectByDailyId(attendanceDailyDto.getId());
                        boolean b = dailyAllTimes.parallelStream()
                                .anyMatch(o -> !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckWorkStatus())
                                        || !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckClosingStatus()));
                        if (!b) {
                            SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
                            updateDailyDto.setId(attendanceDailyDto.getId());
                            updateDailyDto.setCheckStatus(SchedulingAttendanceDailyConstants.CheckStatusEnum.NORMAL.getStatus());
                            updateById(updateDailyDto);
                        }
                    }
                }
            }
        } else if (SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType().equals(applyDto.getApplyType())) {
            // 出差申请
            SchedulingApplyTripRecordDto schedulingApplyTripRecordDto = schedulingApplyTripRecordService.selectById(applyDto.getApplyId());
            if (Objects.nonNull(schedulingApplyTripRecordDto) && SchedulingApplyConstants.APPROVED_STATUS.equals(schedulingApplyTripRecordDto.getStatus())
                    && DateUtil.offsetHour(currentDate, 1).getTime() > DateUtil.beginOfDay(schedulingApplyTripRecordDto.getStartTime()).getTime()) {
                Map<String, Object> params = new HashMap<>();
                params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getPersonId), applyDto.getPersonId());
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStartTime), DateUtils.format(schedulingApplyTripRecordDto.getStartTime(), DateUtils.FormatType.COMMON));
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getEndTime), DateUtils.format(schedulingApplyTripRecordDto.getEndTime(), DateUtils.FormatType.COMMON));
                List<SchedulingAttendanceDailyTimeDto> dailyTimeDtos = schedulingAttendanceDailyTimeService.queryList(params);
                Map<Long, List<SchedulingAttendanceDailyTimeDto>> collect = dailyTimeDtos.parallelStream()
                        .collect(Collectors.groupingBy(SchedulingAttendanceDailyTimeDto::getAttendanceDailyId));

                for (Map.Entry<Long, List<SchedulingAttendanceDailyTimeDto>> longListEntry : collect.entrySet()) {
                    SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                    newlyDailyApplyDto.setApplyId(applyDto.getApplyId());
                    newlyDailyApplyDto.setDailyId(longListEntry.getKey());
                    newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType());
                    newlyDailyApplyDto.setPersonId(applyDto.getPersonId());
                    schedulingAttendanceDailyApplyService.save(newlyDailyApplyDto);

                    BigDecimal incrementTripHour = BigDecimal.ZERO;
                    for (SchedulingAttendanceDailyTimeDto time : longListEntry.getValue()) {
                        if (schedulingApplyTripRecordDto.getEndTime().getTime() > time.getWorkTime().getTime()
                                && schedulingApplyTripRecordDto.getStartTime().getTime() < time.getClosingTime().getTime()) {
                            Date intersectionStartTime = new Date(Math.max(schedulingApplyTripRecordDto.getStartTime().getTime(), time.getWorkTime().getTime()));
                            Date intersectionEndTime = new Date(Math.min(schedulingApplyTripRecordDto.getEndTime().getTime(), time.getClosingTime().getTime()));
                            if (intersectionEndTime.getTime() - intersectionStartTime.getTime() > CAL_TINE_ACCURACY_MILLISECOND) {
                                // 存在交集，并且交集范围大于记录的最小精度
                                BigDecimal increment = new BigDecimal(String.valueOf(intersectionEndTime.getTime() - intersectionStartTime.getTime()))
                                        .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                                incrementTripHour = incrementTripHour.add(increment);
                            }

                            // 出差造成的影响，更新打卡时间段状态
                            SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
                            updateTimeDto.setId(time.getId());
                            if (schedulingApplyTripRecordDto.getEndTime().getTime() >= time.getClosingTime().getTime()) {
                                updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                            }
                            if (schedulingApplyTripRecordDto.getStartTime().getTime() <= time.getWorkTime().getTime()) {
                                updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                            }
                            schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
                        }
                    }

                    SchedulingAttendanceDailyDto attendanceDailyDto = selectCacheById(longListEntry.getKey());

                    if (BigDecimal.ZERO.compareTo(incrementTripHour) < 0) {
                        BigDecimal nowTripHour = Objects.isNull(attendanceDailyDto.getTripHour()) ? BigDecimal.ZERO : attendanceDailyDto.getTripHour();
                        SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
                        updateDailyDto.setId(attendanceDailyDto.getId());
                        updateDailyDto.setTripHour(nowTripHour.add(incrementTripHour));
                        updateById(updateDailyDto);
                    }

                    if (SchedulingAttendanceDailyConstants.CheckStatusEnum.ABNORMAL.getStatus().equals(attendanceDailyDto.getCheckStatus())) {
                        List<SchedulingAttendanceDailyTimeDto> dailyAllTimes = schedulingAttendanceDailyTimeService.selectByDailyId(attendanceDailyDto.getId());
                        boolean b = dailyAllTimes.parallelStream()
                                .anyMatch(o -> !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckWorkStatus())
                                        || !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckClosingStatus()));
                        if (!b) {
                            SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
                            updateDailyDto.setId(attendanceDailyDto.getId());
                            updateDailyDto.setCheckStatus(SchedulingAttendanceDailyConstants.CheckStatusEnum.NORMAL.getStatus());
                            updateById(updateDailyDto);
                        }
                    }
                }
            }
        } else if (SchedulingApplyConstants.ApplyTypeEnum.RENEW_CARD.getType().equals(applyDto.getApplyType())) {
            // 补卡申请
            SchedulingApplyRenewCardRecordDto schedulingApplyRenewCardRecordDto = schedulingApplyRenewCardRecordService.selectById(applyDto.getApplyId());
            if (!SchedulingApplyConstants.APPROVED_STATUS.equals(schedulingApplyRenewCardRecordDto.getStatus())) {
                return;
            }

            DailyCheckReqDto dailyCheckReqDto = new DailyCheckReqDto();
            dailyCheckReqDto.setPersonId(applyDto.getPersonId());
            dailyCheckReqDto.setCheckDate(schedulingApplyRenewCardRecordDto.getRenewCardTime());
            dailyCheckReqDto.setIsField(SchedulingAttendanceDailyConstants.IsFieldEnum.NOT_FIELD.getType());
            dailyCheckReqDto.setIsRepair(SchedulingAttendanceDailyConstants.IsRepairEnum.IS_REPAIR.getType());
            // 打卡请求
            DailyCheckRespDto check = check(dailyCheckReqDto);
            if (Boolean.TRUE.equals(check.getSuccess()) && CollectionUtils.isNotEmpty(check.getDailyList())) {
                List<SchedulingAttendanceDailyApplyDto> collect = check.getDailyList().parallelStream().map(o -> {
                    SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                    newlyDailyApplyDto.setApplyId(o.getId());
                    newlyDailyApplyDto.setDailyId(o.getId());
                    newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.RENEW_CARD.getType());
                    newlyDailyApplyDto.setPersonId(applyDto.getPersonId());
                    return newlyDailyApplyDto;
                }).collect(Collectors.toList());
                schedulingAttendanceDailyApplyService.saveBatch(collect);
            }

        } else if (SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType().equals(applyDto.getApplyType())) {
            // 加班申请
            SchedulingApplyTripRecordDto schedulingApplyTripRecordDto = schedulingApplyTripRecordService.selectById(applyDto.getApplyId());
            if (Objects.nonNull(schedulingApplyTripRecordDto) && SchedulingApplyConstants.APPROVED_STATUS.equals(schedulingApplyTripRecordDto.getStatus())) {
                Map<String, Object> params = new HashMap<>();
                params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getPersonId), applyDto.getPersonId());
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStartTime), DateUtils.format(schedulingApplyTripRecordDto.getStartTime(), DateUtils.FormatType.COMMON));
                params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getEndTime), DateUtils.format(schedulingApplyTripRecordDto.getEndTime(), DateUtils.FormatType.COMMON));
                List<SchedulingAttendanceDailyTimeDto> dailyTimeDtos = schedulingAttendanceDailyTimeService.queryList(params);
                Map<Long, List<SchedulingAttendanceDailyTimeDto>> collect = dailyTimeDtos.parallelStream()
                        .collect(Collectors.groupingBy(SchedulingAttendanceDailyTimeDto::getAttendanceDailyId));

                for (Map.Entry<Long, List<SchedulingAttendanceDailyTimeDto>> longListEntry : collect.entrySet()) {
                    SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                    newlyDailyApplyDto.setApplyId(applyDto.getApplyId());
                    newlyDailyApplyDto.setDailyId(longListEntry.getKey());
                    newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType());
                    newlyDailyApplyDto.setPersonId(applyDto.getPersonId());
                    schedulingAttendanceDailyApplyService.save(newlyDailyApplyDto);
                }
            }
        }

    }

    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.DAILY_CHECK_LOCK + "#{#dailyCheckReqDto.personId}"
            }
    )
    @Transactional
    public DailyCheckRespDto check(DailyCheckReqDto dailyCheckReqDto) {
        final Date checkDate = Objects.isNull(dailyCheckReqDto.getCheckDate()) ? new Date() : dailyCheckReqDto.getCheckDate();
        dailyCheckReqDto.setCheckDate(checkDate);

        DailyCheckRespDto.DailyCheckRespDtoBuilder respBuilder = DailyCheckRespDto.builder().success(Boolean.FALSE)
                .time(DateUtils.format(checkDate, DateUtils.FormatType.TIME));

        String desc = "打卡成功";

        // 该方法会找到正确的考起日，传入的情况不用找，直接打卡这条考勤
        List<SchedulingAttendanceDailyDto> schedulingAttendanceDailyDtos = dailyCheckReqDto.getSchedulingAttendanceDaily() == null
                ? seekDaily(dailyCheckReqDto.getPersonId(), checkDate) : Collections.singletonList(dailyCheckReqDto.getSchedulingAttendanceDaily());
        respBuilder.dailyList(schedulingAttendanceDailyDtos);
        for (SchedulingAttendanceDailyDto attendanceDailyDto : schedulingAttendanceDailyDtos) {

            // 更新记录打卡时间数组
            SchedulingAttendanceDailyDto updateObj = new SchedulingAttendanceDailyDto();
            updateObj.setId(attendanceDailyDto.getId());
            // operateBySite方法内，加班时长计算需要用到考勤组信息
            updateObj.setSchedulingAttendanceGroup(attendanceDailyDto.getSchedulingAttendanceGroup());
            updateObj.setSituation(attendanceDailyDto.getSituation());
            List<String> checkTimes = StringUtils.isNotBlank(attendanceDailyDto.getCheckTimeJson())
                    ? JacksonUtils.parseArray(attendanceDailyDto.getCheckTimeJson(), String.class) : Lists.newArrayList();
            checkTimes.add(DateUtils.format(checkDate, DateUtils.FormatType.COMMON));
            updateObj.setCheckTimeJson(JacksonUtils.toJsonString(checkTimes));

            List<SchedulingAttendanceDailyTimeDto> dailyTimeDtos = attendanceDailyDto.getTimes();
            if (CollectionUtils.isNotEmpty(dailyTimeDtos)) {
                // 配置了时间段，该属性可能要调动
                updateObj.setRealityAttendanceHour(attendanceDailyDto.getRealityAttendanceHour());
                updateObj.setWorkOvertimeHour(attendanceDailyDto.getWorkOvertimeHour());

                for (SchedulingAttendanceDailyTimeDto dailyTimeDto : dailyTimeDtos) {
                    if (DateUtil.compare(checkDate, dailyTimeDto.getStartSignTime()) < 0 || DateUtil.compare(checkDate, dailyTimeDto.getEndSignTime()) > 0) {
                        // 没有落入考勤时间区间内
                        continue;
                    }
                    byte site = calSite(dailyTimeDto, checkDate);
                    if (site < SchedulingAttendanceDailyConstants.TimeSiteEnum.PRE_WORK.getIndex()
                            || site > SchedulingAttendanceDailyConstants.TimeSiteEnum.AFTER_CLOSING.getIndex()) {
                        // 定位在这些位置是不正常的
                        continue;
                    }
                    String lastDesc = operateBySite(updateObj, dailyTimeDto, site, dailyCheckReqDto);
                    if (StringUtils.isNotBlank(lastDesc)) {
                        // 多个上班时间段打卡，一次打卡可能影响多个时间段，响应描述语尽量描述其一
                        desc = lastDesc;
                    }
                }
                // 再次检测考勤是否正常
                boolean b = dailyTimeDtos.parallelStream()
                        .anyMatch(o -> !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckWorkStatus())
                                || !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckClosingStatus()));
                updateObj.setCheckStatus(b ? SchedulingAttendanceDailyConstants.CheckStatusEnum.ABNORMAL.getStatus()
                        : SchedulingAttendanceDailyConstants.CheckStatusEnum.NORMAL.getStatus());
            }
            updateById(updateObj);

            // 留存原始数据
            MongoOriginalCheckRecordDto newlyRecordDto = new MongoOriginalCheckRecordDto();
            newlyRecordDto.setTenantId(attendanceDailyDto.getTenantId());
            newlyRecordDto.setVillageId(attendanceDailyDto.getVillageId());
            newlyRecordDto.setPersonId(dailyCheckReqDto.getPersonId());

            SchedulingAttendanceGroupDto schedulingAttendanceGroupDto = schedulingAttendanceGroupPersonBindingService.selectGroupByPersonId(dailyCheckReqDto.getPersonId());
            Preconditions.checkNotNull(schedulingAttendanceGroupDto);

            newlyRecordDto.setAttendanceGroupId(schedulingAttendanceGroupDto.getId());
            newlyRecordDto.setDailyId(attendanceDailyDto.getId());
            newlyRecordDto.setLongitude(dailyCheckReqDto.getLongitude());
            newlyRecordDto.setLatitude(dailyCheckReqDto.getLatitude());
            newlyRecordDto.setAddress(dailyCheckReqDto.getAddress());
            newlyRecordDto.setIsRepair(dailyCheckReqDto.getIsRepair());
            newlyRecordDto.setIsField(dailyCheckReqDto.getIsField());
            newlyRecordDto.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                    ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                    : null);
            newlyRecordDto.setRemark(dailyCheckReqDto.getRemark());
            newlyRecordDto.setCheckTime(checkDate);

            DeptPersonDto deptPersonDto = deptPersonService.selectById(dailyCheckReqDto.getPersonId());
            Preconditions.checkNotNull(deptPersonDto);

            newlyRecordDto.setDeptPerson(deptPersonDto);

            Preconditions.checkNotNull(deptPersonDto.getDept());

            newlyRecordDto.setDept(deptPersonDto.getDept());
            newlyRecordDto.setSchedulingAttendanceGroup(schedulingAttendanceGroupDto);

            List<SchedulingTimeConfigDto> times = schedulingAttendanceGroupPersonWorkTimeConfigService.workTime(dailyCheckReqDto.getPersonId()
                    , DateUtils.format(attendanceDailyDto.getDate(), DateUtils.FormatType.SIMPLE));

            newlyRecordDto.setTimes(times);
            mongoOriginalCheckRecordService.save(newlyRecordDto);

        }

        respBuilder.success(Boolean.TRUE).desc(desc);
        return respBuilder.build();
    }

    /**
     * 根据定位完成一些具体的操作
     *
     * @param updateDto
     * @param dailyTimeDto
     * @param site
     * @param dailyCheckReqDto
     * @return 打卡描述
     */
    private String operateBySite(SchedulingAttendanceDailyDto updateDto
            , SchedulingAttendanceDailyTimeDto dailyTimeDto, Byte site, DailyCheckReqDto dailyCheckReqDto) {
        String desc = "打卡成功";

        boolean needUpdateDailyTime = false;
        SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
        updateTimeDto.setId(dailyTimeDto.getId());

        // 精度原因(小时，保留两位小数)，避免吞时间问题，大于该毫秒数才记录
        long millisecond = Objects.nonNull(dailyTimeDto.getLastCheckTime()) ? dailyCheckReqDto.getCheckDate().getTime() - dailyTimeDto.getLastCheckTime().getTime() : 0L;
        if (Objects.isNull(dailyTimeDto.getLastCheckTime()) || millisecond > CAL_TINE_ACCURACY_MILLISECOND) {
            needUpdateDailyTime = true;
            updateTimeDto.setLastCheckTime(dailyCheckReqDto.getCheckDate());
        }

        if (SchedulingAttendanceDailyConstants.TimeSiteEnum.PRE_WORK.getIndex().equals(site)
                || SchedulingAttendanceDailyConstants.TimeSiteEnum.AFTER_WORK.getIndex().equals(site)) {
            // 正常上班打卡
            if (!SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(dailyTimeDto.getCheckWorkStatus())
                    || DateUtil.compare(dailyCheckReqDto.getCheckDate(), dailyTimeDto.getCheckWorkTime()) < 0) {
                // 原状态为非正常状态或者有更提前的打卡时间
                needUpdateDailyTime = true;
                updateTimeDto.setCheckWorkTime(dailyCheckReqDto.getCheckDate());
                updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                updateTimeDto.setCheckWorkIsField(dailyCheckReqDto.getIsField());
                updateTimeDto.setCheckWorkIsRepair(dailyCheckReqDto.getIsRepair());
                updateTimeDto.setCheckWorkLongitude(dailyCheckReqDto.getLongitude());
                updateTimeDto.setCheckWorkLatitude(dailyCheckReqDto.getLatitude());
                updateTimeDto.setCheckWorkAddress(dailyCheckReqDto.getAddress());
                if (SchedulingAttendanceDailyConstants.IsFieldEnum.IS_FIELD.getType().equals(dailyCheckReqDto.getIsField())) {
                    SchedulingAttendanceDailyTimeDto.FieldCheckDataCls fieldCheckDataCls = new SchedulingAttendanceDailyTimeDto.FieldCheckDataCls();
                    fieldCheckDataCls.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                            ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                            : new String[]{});
                    fieldCheckDataCls.setRemark(dailyCheckReqDto.getRemark());
                    updateTimeDto.setCheckWorkFieldJson(JacksonUtils.toJsonString(fieldCheckDataCls));
                }
                desc = "上班打卡成功";
                dailyTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
            }
        } else if (SchedulingAttendanceDailyConstants.TimeSiteEnum.LATE.getIndex().equals(site)) {
            // 迟到打卡
            if (SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.LACK.getStatus().equals(dailyTimeDto.getCheckWorkStatus())
                    || DateUtil.compare(dailyCheckReqDto.getCheckDate(), dailyTimeDto.getCheckWorkTime()) < 0) {
                // 原状态为缺卡状态
                needUpdateDailyTime = true;
                updateTimeDto.setCheckWorkTime(dailyCheckReqDto.getCheckDate());
                updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.LATE.getStatus());
                updateTimeDto.setCheckWorkIsField(dailyCheckReqDto.getIsField());
                updateTimeDto.setCheckWorkIsRepair(dailyCheckReqDto.getIsRepair());
                updateTimeDto.setCheckWorkLongitude(dailyCheckReqDto.getLongitude());
                updateTimeDto.setCheckWorkLatitude(dailyCheckReqDto.getLatitude());
                updateTimeDto.setCheckWorkAddress(dailyCheckReqDto.getAddress());
                if (SchedulingAttendanceDailyConstants.IsFieldEnum.IS_FIELD.getType().equals(dailyCheckReqDto.getIsField())) {
                    SchedulingAttendanceDailyTimeDto.FieldCheckDataCls fieldCheckDataCls = new SchedulingAttendanceDailyTimeDto.FieldCheckDataCls();
                    fieldCheckDataCls.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                            ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                            : new String[]{});
                    fieldCheckDataCls.setRemark(dailyCheckReqDto.getRemark());
                    updateTimeDto.setCheckWorkFieldJson(JacksonUtils.toJsonString(fieldCheckDataCls));
                }
                desc = "迟到打卡成功";
                dailyTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.LATE.getStatus());
            }
        } else if (SchedulingAttendanceDailyConstants.TimeSiteEnum.LAKE.getIndex().equals(site)) {
            // 缺勤打卡
            if (dailyTimeDto.getCheckWorkTime() == null) {
                // 上班打卡时间还为 null，还没打过卡，设置一个缺勤时间，上班打卡状态仍然为缺卡
                needUpdateDailyTime = true;
                updateTimeDto.setCheckWorkTime(dailyCheckReqDto.getCheckDate());
                updateTimeDto.setCheckWorkIsField(dailyCheckReqDto.getIsField());
                updateTimeDto.setCheckWorkIsRepair(dailyCheckReqDto.getIsRepair());
                updateTimeDto.setCheckWorkLongitude(dailyCheckReqDto.getLongitude());
                updateTimeDto.setCheckWorkLatitude(dailyCheckReqDto.getLatitude());
                updateTimeDto.setCheckWorkAddress(dailyCheckReqDto.getAddress());
                if (SchedulingAttendanceDailyConstants.IsFieldEnum.IS_FIELD.getType().equals(dailyCheckReqDto.getIsField())) {
                    SchedulingAttendanceDailyTimeDto.FieldCheckDataCls fieldCheckDataCls = new SchedulingAttendanceDailyTimeDto.FieldCheckDataCls();
                    fieldCheckDataCls.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                            ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                            : new String[]{});
                    fieldCheckDataCls.setRemark(dailyCheckReqDto.getRemark());
                    updateTimeDto.setCheckWorkFieldJson(JacksonUtils.toJsonString(fieldCheckDataCls));
                }
            } else if (DateUtil.compare(dailyCheckReqDto.getCheckDate(), dailyTimeDto.getCheckClosingTime()) > 0) {
                needUpdateDailyTime = true;
                updateTimeDto.setCheckClosingTime(dailyCheckReqDto.getCheckDate());
                updateTimeDto.setCheckClosingIsField(dailyCheckReqDto.getIsField());
                updateTimeDto.setCheckClosingIsRepair(dailyCheckReqDto.getIsRepair());
                updateTimeDto.setCheckClosingLongitude(dailyCheckReqDto.getLongitude());
                updateTimeDto.setCheckClosingLatitude(dailyCheckReqDto.getLatitude());
                updateTimeDto.setCheckClosingAddress(dailyCheckReqDto.getAddress());
                if (SchedulingAttendanceDailyConstants.IsFieldEnum.IS_FIELD.getType().equals(dailyCheckReqDto.getIsField())) {
                    SchedulingAttendanceDailyTimeDto.FieldCheckDataCls fieldCheckDataCls = new SchedulingAttendanceDailyTimeDto.FieldCheckDataCls();
                    fieldCheckDataCls.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                            ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                            : new String[]{});
                    fieldCheckDataCls.setRemark(dailyCheckReqDto.getRemark());
                    updateTimeDto.setCheckClosingFieldJson(JacksonUtils.toJsonString(fieldCheckDataCls));
                }
            }
            desc = "缺勤打卡";
        } else if (SchedulingAttendanceDailyConstants.TimeSiteEnum.EARLY.getIndex().equals(site)) {
            // 早退打卡
            if (SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.LACK.getStatus().equals(dailyTimeDto.getCheckClosingStatus())
                    || DateUtil.compare(dailyCheckReqDto.getCheckDate(), dailyTimeDto.getCheckClosingTime()) > 0) {
                // 原状态为缺卡状态
                needUpdateDailyTime = true;
                updateTimeDto.setCheckClosingTime(dailyCheckReqDto.getCheckDate());
                updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.EARLY.getStatus());
                updateTimeDto.setCheckClosingIsField(dailyCheckReqDto.getIsField());
                updateTimeDto.setCheckClosingIsRepair(dailyCheckReqDto.getIsRepair());
                updateTimeDto.setCheckClosingLongitude(dailyCheckReqDto.getLongitude());
                updateTimeDto.setCheckClosingLatitude(dailyCheckReqDto.getLatitude());
                updateTimeDto.setCheckClosingAddress(dailyCheckReqDto.getAddress());
                if (SchedulingAttendanceDailyConstants.IsFieldEnum.IS_FIELD.getType().equals(dailyCheckReqDto.getIsField())) {
                    SchedulingAttendanceDailyTimeDto.FieldCheckDataCls fieldCheckDataCls = new SchedulingAttendanceDailyTimeDto.FieldCheckDataCls();
                    fieldCheckDataCls.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                            ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                            : new String[]{});
                    fieldCheckDataCls.setRemark(dailyCheckReqDto.getRemark());
                    updateTimeDto.setCheckClosingFieldJson(JacksonUtils.toJsonString(fieldCheckDataCls));
                }
                desc = "早退打卡成功";
                dailyTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.EARLY.getStatus());
            }
        } else if (SchedulingAttendanceDailyConstants.TimeSiteEnum.PRE_CLOSING.getIndex().equals(site)
                || SchedulingAttendanceDailyConstants.TimeSiteEnum.AFTER_CLOSING.getIndex().equals(site)) {
            // 正常下班打卡，下探，均更新
            if (!SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(dailyTimeDto.getCheckClosingStatus())
                    || DateUtil.compare(dailyCheckReqDto.getCheckDate(), dailyTimeDto.getCheckClosingTime()) > 0) {
                needUpdateDailyTime = true;
                updateTimeDto.setCheckClosingTime(dailyCheckReqDto.getCheckDate());
                updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                updateTimeDto.setCheckClosingIsField(dailyCheckReqDto.getIsField());
                updateTimeDto.setCheckClosingIsRepair(dailyCheckReqDto.getIsRepair());
                updateTimeDto.setCheckClosingLongitude(dailyCheckReqDto.getLongitude());
                updateTimeDto.setCheckClosingLatitude(dailyCheckReqDto.getLatitude());
                updateTimeDto.setCheckClosingAddress(dailyCheckReqDto.getAddress());
                if (SchedulingAttendanceDailyConstants.IsFieldEnum.IS_FIELD.getType().equals(dailyCheckReqDto.getIsField())) {
                    SchedulingAttendanceDailyTimeDto.FieldCheckDataCls fieldCheckDataCls = new SchedulingAttendanceDailyTimeDto.FieldCheckDataCls();
                    fieldCheckDataCls.setPics(StringUtils.isNotBlank(dailyCheckReqDto.getPics())
                            ? JacksonUtils.parseArray(dailyCheckReqDto.getPics(), String.class).toArray(new String[0])
                            : new String[]{});
                    fieldCheckDataCls.setRemark(dailyCheckReqDto.getRemark());
                    updateTimeDto.setCheckClosingFieldJson(JacksonUtils.toJsonString(fieldCheckDataCls));
                }
                desc = "下班打卡成功";
                dailyTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
            }
        }

        // 大于最小计时单位 且 上班打卡状态非缺卡，缺勤不记录出勤时间
        if (millisecond > CAL_TINE_ACCURACY_MILLISECOND
                && !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.LACK.getStatus().equals(dailyTimeDto.getCheckWorkStatus())) {
            BigDecimal increment = new BigDecimal(String.valueOf(millisecond))
                    .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
            BigDecimal realityAttendanceHour = Objects.isNull(updateDto.getRealityAttendanceHour()) ? BigDecimal.ZERO : updateDto.getRealityAttendanceHour();
            updateDto.setRealityAttendanceHour(realityAttendanceHour.add(increment));
        }

        // 加班规则
        SchedulingWorkOvertimeConfigDto schedulingWorkOvertimeConfigDto = schedulingWorkOvertimeConfigService.selectCacheById(updateDto.getSchedulingAttendanceGroup().getWorkOvertimeId());
        if (schedulingWorkOvertimeConfigDto != null && SystemConstants.ENABLE.equals(schedulingWorkOvertimeConfigDto.getStatus())) {
            // 加班规则可用才计算加班

            BigDecimal presentWorkOvertimeHour = BigDecimal.ZERO;

            if (millisecond > CAL_TINE_ACCURACY_MILLISECOND
                    && SchedulingAttendanceDailyConstants.TimeSiteEnum.AFTER_CLOSING.getIndex().equals(site)
                    && SchedulingAttendanceDailyConstants.SituationTypeEnum.WORK.getType().equals(updateDto.getSituation())) {
                // 工作日加班
                SchedulingWorkOvertimeConfigDto.WorkDayConfig workDayConfig = JacksonUtils.parse(schedulingWorkOvertimeConfigDto.getWorkDayConfigJson(), SchedulingWorkOvertimeConfigDto.WorkDayConfig.class);
                int afterWorkTime = workDayConfig.getAfterWorkTime() == null ? 0 : workDayConfig.getAfterWorkTime();
                long afterWorkBetween = DateUtil.between(dailyTimeDto.getClosingTime(), dailyCheckReqDto.getCheckDate(), DateUnit.MINUTE, Boolean.TRUE);
                if (SystemConstants.ENABLE.equals(workDayConfig.getStatus()) && afterWorkBetween > afterWorkTime && workDayConfig.getWorkOvertimeType() != null) {
                    Date calDate = Objects.isNull(dailyTimeDto.getLastCheckTime()) || DateUtil.compare(DateUtil.offsetMinute(dailyTimeDto.getClosingTime(), afterWorkTime), dailyTimeDto.getLastCheckTime()) > 0
                            ? DateUtil.offsetMinute(dailyTimeDto.getClosingTime(), afterWorkTime) : dailyTimeDto.getLastCheckTime();

                    if (workDayConfig.getWorkOvertimeType().equals((byte) 2)) {
                        // 在申请时段内打卡，需要再取交集
                        List<SchedulingAttendanceDailyApplyDto> overTimeApplyDtos = schedulingAttendanceDailyApplyService.selectCacheByDailyId(dailyTimeDto.getAttendanceDailyId()).stream()
                                .filter(o -> SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType().equals(o.getApplyType()))
                                .collect(Collectors.toList());

                        long intersection = 0;
                        for (SchedulingAttendanceDailyApplyDto overTimeApplyDto : overTimeApplyDtos) {
                            SchedulingApplyTripRecordDto schedulingApplyTripRecordDto = schedulingApplyTripRecordService.selectById(overTimeApplyDto.getApplyId());
                            long max = Math.max(calDate.getTime(), schedulingApplyTripRecordDto.getStartTime().getTime());
                            long min = Math.min(dailyCheckReqDto.getCheckDate().getTime(), schedulingApplyTripRecordDto.getEndTime().getTime());
                            if (min >= max) {
                                intersection += (min - max);
                            }
                        }

                        presentWorkOvertimeHour = new BigDecimal(intersection + "")
                                .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                    } else if (workDayConfig.getWorkOvertimeType().equals((byte) 3)) {
                        presentWorkOvertimeHour = new BigDecimal(String.valueOf(dailyCheckReqDto.getCheckDate().getTime() - calDate.getTime()))
                                .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                    }
                }
            } else if (millisecond > CAL_TINE_ACCURACY_MILLISECOND && SchedulingAttendanceDailyConstants.SituationTypeEnum.REST.getType().equals(updateDto.getSituation())) {
                // 休息日加班
                SchedulingWorkOvertimeConfigDto.RestDayConfig restDayConfig = JacksonUtils.parse(schedulingWorkOvertimeConfigDto.getRestDayConfigJson(), SchedulingWorkOvertimeConfigDto.RestDayConfig.class);

                if (restDayConfig.getWorkOvertimeType().equals((byte) 2)) {
                    // 在申请时段内打卡，需要再取交集
                    List<SchedulingAttendanceDailyApplyDto> overTimeApplyDtos = schedulingAttendanceDailyApplyService.selectCacheByDailyId(dailyTimeDto.getAttendanceDailyId()).stream()
                            .filter(o -> SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType().equals(o.getApplyType()))
                            .collect(Collectors.toList());

                    long intersection = 0;
                    for (SchedulingAttendanceDailyApplyDto overTimeApplyDto : overTimeApplyDtos) {
                        SchedulingApplyTripRecordDto schedulingApplyTripRecordDto = schedulingApplyTripRecordService.selectById(overTimeApplyDto.getApplyId());
                        long max = Math.max(dailyTimeDto.getLastCheckTime().getTime(), schedulingApplyTripRecordDto.getStartTime().getTime());
                        long min = Math.min(dailyCheckReqDto.getCheckDate().getTime(), schedulingApplyTripRecordDto.getEndTime().getTime());
                        if (min >= max) {
                            intersection += (min - max);
                        }
                    }

                    presentWorkOvertimeHour = new BigDecimal(intersection + "")
                            .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                } else if (restDayConfig.getWorkOvertimeType().equals((byte) 3)) {
                    presentWorkOvertimeHour = new BigDecimal(String.valueOf(dailyCheckReqDto.getCheckDate().getTime() - dailyTimeDto.getLastCheckTime().getTime()))
                            .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                }

            } else if (millisecond > CAL_TINE_ACCURACY_MILLISECOND && SchedulingAttendanceDailyConstants.SituationTypeEnum.VACATION.getType().equals(updateDto.getSituation())) {
                // 节假日加班
                SchedulingWorkOvertimeConfigDto.HolidayDayConfig holidayDayConfig = JacksonUtils.parse(schedulingWorkOvertimeConfigDto.getHolidayConfigJson(), SchedulingWorkOvertimeConfigDto.HolidayDayConfig.class);

                if (holidayDayConfig.getWorkOvertimeType().equals((byte) 2)) {
                    // 在申请时段内打卡，需要再取交集
                    List<SchedulingAttendanceDailyApplyDto> overTimeApplyDtos = schedulingAttendanceDailyApplyService.selectCacheByDailyId(dailyTimeDto.getAttendanceDailyId()).stream()
                            .filter(o -> SchedulingApplyConstants.ApplyTypeEnum.OVER_TIME.getType().equals(o.getApplyType()))
                            .collect(Collectors.toList());

                    long intersection = 0;
                    for (SchedulingAttendanceDailyApplyDto overTimeApplyDto : overTimeApplyDtos) {
                        SchedulingApplyTripRecordDto schedulingApplyTripRecordDto = schedulingApplyTripRecordService.selectById(overTimeApplyDto.getApplyId());
                        long max = Math.max(dailyTimeDto.getLastCheckTime().getTime(), schedulingApplyTripRecordDto.getStartTime().getTime());
                        long min = Math.min(dailyCheckReqDto.getCheckDate().getTime(), schedulingApplyTripRecordDto.getEndTime().getTime());
                        if (min >= max) {
                            intersection += (min - max);
                        }
                    }

                    presentWorkOvertimeHour = new BigDecimal(intersection + "")
                            .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                } else if (holidayDayConfig.getWorkOvertimeType().equals((byte) 3)) {
                    presentWorkOvertimeHour = new BigDecimal(String.valueOf(dailyCheckReqDto.getCheckDate().getTime() - dailyTimeDto.getLastCheckTime().getTime()))
                            .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                }
            }

            // 根据加班规则其他设置，计算是否超限
            boolean noLimitWorkOvertime = Boolean.TRUE;
            SchedulingWorkOvertimeConfigDto.OtherConfig otherConfig = JacksonUtils.parse(schedulingWorkOvertimeConfigDto.getOtherConfigJson(), SchedulingWorkOvertimeConfigDto.OtherConfig.class);
            if (Objects.nonNull(otherConfig) && CollectionUtils.isNotEmpty(otherConfig.getCycleObj())) {
                // 查看是否触发限制了
                Map<String, Object> queryDurationDailyMap = new HashMap<>();
                for (SchedulingWorkOvertimeConfigDto.CycleRule cycleRule : otherConfig.getCycleObj()) {
                    Byte duration = cycleRule.getDuration();
                    Integer limitTime = (cycleRule.getLimitTime() == null || cycleRule.getLimitTime() < 0) ? 0 : cycleRule.getLimitTime();
                    List<Byte> cycleLimits = cycleRule.getCycleLimits() == null ? Lists.newArrayList() : cycleRule.getCycleLimits();
                    if (duration == null || !cycleLimits.contains(updateDto.getSituation())) {
                        // 限制的其他考勤状况，可以直接跳过
                        continue;
                    }
                    if (duration.equals((byte) 1)) {
                        // 日
                        queryDurationDailyMap.put(SystemConstants.TIME_SLOT_KEY_PAIR.getLeft(), DateUtils.format(DateUtil.beginOfDay(dailyCheckReqDto.getCheckDate()), DateUtils.FormatType.COMMON));
                        queryDurationDailyMap.put(SystemConstants.TIME_SLOT_KEY_PAIR.getRight(), DateUtils.format(DateUtil.endOfDay(dailyCheckReqDto.getCheckDate()), DateUtils.FormatType.COMMON));
                    } else if (duration.equals((byte) 2)) {
                        // 周
                        queryDurationDailyMap.put(SystemConstants.TIME_SLOT_KEY_PAIR.getLeft(), DateUtils.format(DateUtil.beginOfWeek(dailyCheckReqDto.getCheckDate()), DateUtils.FormatType.COMMON));
                        queryDurationDailyMap.put(SystemConstants.TIME_SLOT_KEY_PAIR.getRight(), DateUtils.format(DateUtil.endOfWeek(dailyCheckReqDto.getCheckDate()), DateUtils.FormatType.COMMON));
                    } else if (duration.equals((byte) 3)) {
                        // 月
                        queryDurationDailyMap.put(SystemConstants.TIME_SLOT_KEY_PAIR.getLeft(), DateUtils.format(DateUtil.beginOfMonth(dailyCheckReqDto.getCheckDate()), DateUtils.FormatType.COMMON));
                        queryDurationDailyMap.put(SystemConstants.TIME_SLOT_KEY_PAIR.getRight(), DateUtils.format(DateUtil.endOfMonth(dailyCheckReqDto.getCheckDate()), DateUtils.FormatType.COMMON));
                    } else {
                        continue;
                    }

                    List<SchedulingAttendanceDailyDto> schedulingAttendanceDailyDtos = queryList(queryDurationDailyMap);
                    // 计算设置限制的加班时长
                    BigDecimal durationWorkOvertimeHours = schedulingAttendanceDailyDtos.parallelStream()
                            .filter(o -> cycleLimits.contains(o.getSituation()))
                            .map(SchedulingAttendanceDailyDto::getWorkOvertimeHour)
                            .reduce(BigDecimal.ZERO, BigDecimal::add);
                    if (durationWorkOvertimeHours.add(presentWorkOvertimeHour).compareTo(new BigDecimal(limitTime + "")) >= 0) {
                        // 达到上限了
                        noLimitWorkOvertime = Boolean.FALSE;
                        break;
                    }
                }
            }

            BigDecimal workOvertimeHour = Objects.isNull(updateDto.getWorkOvertimeHour()) ? BigDecimal.ZERO : updateDto.getWorkOvertimeHour();
            if (Boolean.TRUE.equals(noLimitWorkOvertime)) {
                updateDto.setWorkOvertimeHour(workOvertimeHour.add(presentWorkOvertimeHour));
            }
            BigDecimal ignoreLimitWorkOvertimeHour = Objects.isNull(updateDto.getIgnoreLimitWorkOvertimeHour()) ? BigDecimal.ZERO : updateDto.getIgnoreLimitWorkOvertimeHour();
            updateDto.setIgnoreLimitWorkOvertimeHour(ignoreLimitWorkOvertimeHour.add(presentWorkOvertimeHour));


        }

        if (needUpdateDailyTime) {
            schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
        }
        return desc;
    }

    /**
     * 定位打卡时间位置
     *
     * @param dailyTimeDto
     * @param checkDate
     * @return 0 为异常情况
     */
    private byte calSite(SchedulingAttendanceDailyTimeDto dailyTimeDto, Date checkDate) {
        SchedulingAttendanceDailyTimeMiniCls schedulingAttendanceDailyTimeMiniCls = CopyUtils.copyObj(dailyTimeDto, SchedulingAttendanceDailyTimeMiniCls.class);
        List<Date> dateList = new ArrayList<>(Collections.singletonList(checkDate));
        Field[] fields = ReflectUtil.getFields(SchedulingAttendanceDailyTimeMiniCls.class);
        for (Field field : fields) {
            Object fieldValue = ReflectUtil.getFieldValue(schedulingAttendanceDailyTimeMiniCls, field);
            if (Objects.isNull(fieldValue)) {
                return 0;
            }
            dateList.add((Date) fieldValue);
        }
        dateList.sort(Date::compareTo);
        return (byte) Arrays.binarySearch(dateList.toArray(), checkDate);
    }

    @Data
    public static class SchedulingAttendanceDailyTimeMiniCls {
        /**
         * 最早开始签到时间
         */
        private Date startSignTime;

        /**
         * 上班时间
         */
        private Date workTime;

        /**
         * 迟到时间
         */
        private Date lateTime;

        /**
         * 记上班缺卡时间
         */
        private Date missingWorkTime;

        /**
         * 记下班缺卡时间
         */
        private Date missingClosingTime;

        /**
         * 记早退时间
         */
        private Date earlyLeaveTime;

        /**
         * 下班时间
         */
        private Date closingTime;

        /**
         * 最晚签退时间
         */
        private Date endSignTime;
    }

    @Override
    @Transactional
    public SchedulingAttendanceDailyDto gen(AttendanceDailyGenReq genReq) {
        final Date currentDate = new Date();

        SchedulingAttendanceGroupDto schedulingAttendanceGroupDto = schedulingAttendanceGroupPersonBindingService.selectGroupByPersonId(genReq.getPersonId());
        Preconditions.checkNotNull(schedulingAttendanceGroupDto);

        List<SchedulingTimeConfigDto> times = schedulingAttendanceGroupPersonWorkTimeConfigService.workTime(genReq.getPersonId(), genReq.getDataStr());
        BigDecimal attendanceHour = BigDecimal.ZERO;
        if (CollectionUtils.isNotEmpty(times)) {
            for (SchedulingTimeConfigDto time : times) {
                Date workDate = DateUtils.parse(String.format("%s %s", genReq.getDataStr(), time.getWorkTime()), DateUtils.FormatType.COMMON);
                Date closingDate = DateUtils.parse(String.format("%s %s", genReq.getDataStr(), time.getClosingTime()), DateUtils.FormatType.COMMON);

                assert workDate != null;
                assert closingDate != null;
                long offset = workDate.getTime() < closingDate.getTime()
                        ? DateUtil.between(workDate, closingDate, DateUnit.MINUTE, Boolean.TRUE)
                        : DateUtil.between(workDate, DateUtil.offsetDay(closingDate, 1), DateUnit.MINUTE, Boolean.TRUE);
                attendanceHour = attendanceHour.add(new BigDecimal(String.valueOf(offset)));
            }
        }

        SchedulingAttendanceDailyDto newlyDto = new SchedulingAttendanceDailyDto();
        newlyDto.setTenantId(schedulingAttendanceGroupDto.getTenantId());
        newlyDto.setVillageId(schedulingAttendanceGroupDto.getVillageId());
        newlyDto.setSchedulingAttendanceGroupId(schedulingAttendanceGroupDto.getId());
        newlyDto.setSchedulingAttendanceGroup(schedulingAttendanceGroupDto);
        newlyDto.setPersonId(genReq.getPersonId());
        DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(genReq.getPersonId());
        if (Objects.nonNull(deptPersonDto)) {
            // 冗余人员信息
            newlyDto.setPersonName(deptPersonDto.getName());
            newlyDto.setPersonJobNo(deptPersonDto.getJobNo());
        }

        // 非固定班，根据排班确定班休
        if (CollectionUtils.isNotEmpty(times)) {
            newlyDto.setSituation(SchedulingAttendanceDailyConstants.SituationTypeEnum.WORK.getType());
        } else {
            newlyDto.setSituation(SchedulingAttendanceDailyConstants.SituationTypeEnum.REST.getType());
        }
        if (SchedulingAttendanceGroupConstants.SchedulingTypeEnum.FIX.getType().equals(schedulingAttendanceGroupDto.getShiftSystemType())) {
            // 固定班，日历可能更改班休假状态
            SchedulingCalendarConfigBindingDto schedulingCalendarConfigBindingDto = schedulingCalendarConfigBindingService.selectListByConfigId(schedulingAttendanceGroupDto.getSchedulingCalendarId())
                    .parallelStream()
                    .filter(o -> DateUtils.format(o.getDate(), DateUtils.FormatType.SIMPLE).equals(genReq.getDataStr()))
                    .findFirst()
                    .orElse(null);
            if (Objects.nonNull(schedulingCalendarConfigBindingDto)
                    && SchedulingCalendarConfigConstants.DateType.HOLIDAY.getType().equals(schedulingCalendarConfigBindingDto.getType())) {
                newlyDto.setSituation(SchedulingAttendanceDailyConstants.SituationTypeEnum.VACATION.getType());
            } else if (Objects.nonNull(schedulingCalendarConfigBindingDto)
                    && SchedulingCalendarConfigConstants.DateType.REST.getType().equals(schedulingCalendarConfigBindingDto.getType())) {
                newlyDto.setSituation(SchedulingAttendanceDailyConstants.SituationTypeEnum.REST.getType());
            }
        }

        newlyDto.setDate(DateUtils.parse(genReq.getDataStr(), DateUtils.FormatType.SIMPLE));
        newlyDto.setCheckStatus(CollectionUtils.isNotEmpty(times)
                ? SchedulingAttendanceDailyConstants.CheckStatusEnum.ABNORMAL.getStatus()
                : SchedulingAttendanceDailyConstants.CheckStatusEnum.NORMAL.getStatus());
        newlyDto.setAttendanceHour(attendanceHour.divide(new BigDecimal("60"), 2, RoundingMode.HALF_UP));
        newlyDto.setRealityAttendanceHour(BigDecimal.ZERO);
        newlyDto.setWorkOvertimeHour(BigDecimal.ZERO);
        newlyDto.setIgnoreLimitWorkOvertimeHour(BigDecimal.ZERO);
        newlyDto.setCheckTimeJson("[]");
        newlyDto.setCreateTime(currentDate);

        final Long save = save(newlyDto);

        if (CollectionUtils.isNotEmpty(times)) {
            List<SchedulingAttendanceDailyTimeDto> timeDtoList = times.parallelStream().map(o -> {
                SchedulingAttendanceDailyTimeDto schedulingAttendanceDailyTimeDto = new SchedulingAttendanceDailyTimeDto();
                schedulingAttendanceDailyTimeDto.setAttendanceDailyId(save);
                schedulingAttendanceDailyTimeDto.setName(o.getName());
                schedulingAttendanceDailyTimeDto.setPersonId(genReq.getPersonId());

                // 保存考勤时间点
                schedulingAttendanceDailyTimeDto.setWorkTime(DateUtils.parse(String.format("%s %s", genReq.getDataStr(), o.getWorkTime()), DateUtils.FormatType.COMMON));

                schedulingAttendanceDailyTimeDto.setStartSignTime(realDate(o.getStartSignTime(), schedulingAttendanceDailyTimeDto.getWorkTime(), SystemConstants.DISABLE));
                schedulingAttendanceDailyTimeDto.setLateTime(realDate(o.getLateTime(), schedulingAttendanceDailyTimeDto.getWorkTime(), SystemConstants.ENABLE));
                schedulingAttendanceDailyTimeDto.setMissingWorkTime(realDate(o.getMissingWorkTime(), schedulingAttendanceDailyTimeDto.getLateTime(), SystemConstants.ENABLE));
                schedulingAttendanceDailyTimeDto.setMissingClosingTime(realDate(o.getMissingClosingTime(), schedulingAttendanceDailyTimeDto.getMissingWorkTime(), SystemConstants.ENABLE));
                schedulingAttendanceDailyTimeDto.setEarlyLeaveTime(realDate(o.getEarlyLeaveTime(), schedulingAttendanceDailyTimeDto.getMissingClosingTime(), SystemConstants.ENABLE));
                schedulingAttendanceDailyTimeDto.setClosingTime(realDate(o.getClosingTime(), schedulingAttendanceDailyTimeDto.getEarlyLeaveTime(), SystemConstants.ENABLE));
                schedulingAttendanceDailyTimeDto.setEndSignTime(realDate(o.getEndSignTime(), schedulingAttendanceDailyTimeDto.getClosingTime(), SystemConstants.ENABLE));
                List<SchedulingAttendanceDailyTimeDto.RestTimeSlotCls> restTimes = Lists.newArrayList();

                // 保存休息时段
                if (StringUtils.isNotBlank(o.getRestTimeJson())) {
                    List<String> restTimeConfigList = JacksonUtils.parseArray(o.getRestTimeJson(), String.class);
                    for (String restTime : restTimeConfigList) {
                        String[] split = restTime.split("-");
                        SchedulingAttendanceDailyTimeDto.RestTimeSlotCls restTimeSlotCls = new SchedulingAttendanceDailyTimeDto.RestTimeSlotCls();
                        restTimeSlotCls.setRestStartTime(realDate(split[0] + ":00", schedulingAttendanceDailyTimeDto.getWorkTime(), SystemConstants.ENABLE));
                        restTimeSlotCls.setRestEndTime(realDate(split[1] + ":00", restTimeSlotCls.getRestStartTime(), SystemConstants.ENABLE));
                        restTimes.add(restTimeSlotCls);
                    }
                }
                schedulingAttendanceDailyTimeDto.setRestTimeJson(JacksonUtils.toJsonString(restTimes));
                return schedulingAttendanceDailyTimeDto;
            }).collect(Collectors.toList());
            schedulingAttendanceDailyTimeService.saveBatch(timeDtoList);
        }

        newlyDto.setId(save);
        List<SchedulingAttendanceDailyTimeDto> timeDtos = schedulingAttendanceDailyTimeService.selectByDailyId(save);
        newlyDto.setTimes(timeDtos);

        // 新产生的考勤，可能受到申请的影响
        effect(newlyDto);

        return newlyDto;
    }

    // 新的考勤，检查是否受申请影响
    private void effect(SchedulingAttendanceDailyDto dailyDto) {
        if (CollectionUtils.isEmpty(dailyDto.getTimes())) {
            return;
        }
        // 可能更新，请假时长，出差时长，外出时长等信息
        Boolean needUpdate = Boolean.FALSE;
        SchedulingAttendanceDailyDto updateDailyDto = new SchedulingAttendanceDailyDto();
        updateDailyDto.setId(dailyDto.getId());

        List<Date> dateList = new ArrayList<>();
        for (SchedulingAttendanceDailyTimeDto time : dailyDto.getTimes()) {
            dateList.add(time.getWorkTime());
            dateList.add(time.getClosingTime());
        }
        // 最小的上班时间，最大的下班时间去找申请
        Date[] minMaxDatesHelper = findMinMaxDatesHelper(dateList, 0, dateList.size() - 1);

        Map<String, Object> params = new HashMap<>();
        params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getWorkTime), DateUtils.format(minMaxDatesHelper[0], DateUtils.FormatType.COMMON));
        params.put(LambdaUtil.getFieldName(SchedulingAttendanceDailyTimeDto::getClosingTime), DateUtils.format(minMaxDatesHelper[1], DateUtils.FormatType.COMMON));
        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getApplyDeptPersonId), dailyDto.getPersonId());
        params.put(LambdaUtil.getFieldName(SchedulingApplyLeaveRecordDto::getStatus), String.valueOf(SchedulingApplyConstants.APPROVED_STATUS));

        List<SchedulingApplyLeaveRecordDto> leaveRecordDtos = schedulingApplyLeaveRecordService.queryList(params);
        if (CollectionUtils.isNotEmpty(leaveRecordDtos)) {
            List<SchedulingAttendanceDailyApplyDto> collect = leaveRecordDtos.parallelStream().map(o -> {
                SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                newlyDailyApplyDto.setApplyId(o.getId());
                newlyDailyApplyDto.setDailyId(dailyDto.getId());
                newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.APPLY_LEAVE.getType());
                newlyDailyApplyDto.setPersonId(dailyDto.getPersonId());
                return newlyDailyApplyDto;
            }).collect(Collectors.toList());
            // 绑考勤和请假申请的关系
            schedulingAttendanceDailyApplyService.saveBatch(collect);
        }
        // 找到交集部分，计算真正起效的请假时长
        BigDecimal incrementLeaveHour = BigDecimal.ZERO;
        for (SchedulingApplyLeaveRecordDto leaveRecordDto : leaveRecordDtos) {

            // 请假有效时间
            BigDecimal recordIncrementLeaveHour = BigDecimal.ZERO;

            // 需要计算此次申请，花费总时间
            BigDecimal allSpendHour = BigDecimal.ZERO;

            // 计算游标时间
            Date calCursorDate = leaveRecordDto.getCalCursorTime() == null ? leaveRecordDto.getStartTime() : leaveRecordDto.getCalCursorTime();

            Date maxClosingTime = null;
            for (SchedulingAttendanceDailyTimeDto time : dailyDto.getTimes()) {
                if (maxClosingTime == null || DateUtil.compare(time.getClosingTime(), maxClosingTime) > 0) {
                    maxClosingTime = time.getClosingTime();
                }
                if (leaveRecordDto.getEndTime().getTime() > time.getWorkTime().getTime()
                        && leaveRecordDto.getStartTime().getTime() < time.getClosingTime().getTime()) {
                    Date intersectionStartTime = new Date(Math.max(leaveRecordDto.getStartTime().getTime(), time.getWorkTime().getTime()));
                    Date intersectionEndTime = new Date(Math.min(leaveRecordDto.getEndTime().getTime(), time.getClosingTime().getTime()));
                    if (intersectionEndTime.getTime() - intersectionStartTime.getTime() > CAL_TINE_ACCURACY_MILLISECOND) {
                        // 存在交集，并且交集范围大于记录的最小精度
                        BigDecimal increment = new BigDecimal(String.valueOf(intersectionEndTime.getTime() - intersectionStartTime.getTime()))
                                .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                        incrementLeaveHour = incrementLeaveHour.add(increment);
                        recordIncrementLeaveHour = recordIncrementLeaveHour.add(increment);
                    }

                    // 请假造成的影响，更新打卡时间段状态
                    SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
                    updateTimeDto.setId(time.getId());
                    if (leaveRecordDto.getEndTime().getTime() >= time.getClosingTime().getTime()) {
                        updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                    }
                    if (leaveRecordDto.getStartTime().getTime() <= time.getWorkTime().getTime()) {
                        updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                    }
                    schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
                }
            }
            if (maxClosingTime != null) {
                Date calEndDate = new Date(Math.min(leaveRecordDto.getEndTime().getTime(), maxClosingTime.getTime()));
                allSpendHour = new BigDecimal(String.valueOf(calEndDate.getTime() - calCursorDate.getTime()))
                        .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                calCursorDate = calEndDate;
            }

            // 其他操作
            //更新记录详情
            handleLeaveBalance(dailyDto.getPersonId(), dailyDto.getId(), leaveRecordDto.getId(), allSpendHour.subtract(recordIncrementLeaveHour), SchedulingLeaveConstants.TimeUnitEnum.HOUR.getUnit(), calCursorDate);

        }
        if (BigDecimal.ZERO.compareTo(incrementLeaveHour) < 0) {
            needUpdate = Boolean.TRUE;
            BigDecimal nowLeaveHour = Objects.isNull(dailyDto.getLeaveHour()) ? BigDecimal.ZERO : dailyDto.getLeaveHour();
            updateDailyDto.setLeaveHour(nowLeaveHour.add(incrementLeaveHour));
        }

        // 用同样的方式找到出差和外出，并计算时长
        List<SchedulingApplyOutRecordDto> outRecordDtos = schedulingApplyOutRecordService.queryList(params);
        if (CollectionUtils.isNotEmpty(outRecordDtos)) {
            List<SchedulingAttendanceDailyApplyDto> collect = outRecordDtos.parallelStream().map(o -> {
                SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                newlyDailyApplyDto.setApplyId(o.getId());
                newlyDailyApplyDto.setDailyId(dailyDto.getId());
                newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.OUT.getType());
                newlyDailyApplyDto.setPersonId(dailyDto.getPersonId());
                return newlyDailyApplyDto;
            }).collect(Collectors.toList());
            // 绑考勤和外出申请的关系
            schedulingAttendanceDailyApplyService.saveBatch(collect);
        }
        // 找到交集部分，计算真正起效的请假时长
        BigDecimal incrementOutHour = BigDecimal.ZERO;
        for (SchedulingApplyOutRecordDto outRecordDto : outRecordDtos) {
            for (SchedulingAttendanceDailyTimeDto time : dailyDto.getTimes()) {
                if (outRecordDto.getEndTime().getTime() > time.getWorkTime().getTime()
                        && outRecordDto.getStartTime().getTime() < time.getClosingTime().getTime()) {
                    Date intersectionStartTime = new Date(Math.max(outRecordDto.getStartTime().getTime(), time.getWorkTime().getTime()));
                    Date intersectionEndTime = new Date(Math.min(outRecordDto.getEndTime().getTime(), time.getClosingTime().getTime()));
                    if (intersectionEndTime.getTime() - intersectionStartTime.getTime() > CAL_TINE_ACCURACY_MILLISECOND) {
                        // 存在交集，并且交集范围大于记录的最小精度
                        BigDecimal increment = new BigDecimal(String.valueOf(intersectionEndTime.getTime() - intersectionStartTime.getTime()))
                                .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                        incrementOutHour = incrementOutHour.add(increment);
                    }
                    // 请假造成的影响，更新打卡时间段状态
                    SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
                    updateTimeDto.setId(time.getId());
                    if (outRecordDto.getEndTime().getTime() >= time.getClosingTime().getTime()) {
                        updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                    }
                    if (outRecordDto.getStartTime().getTime() <= time.getWorkTime().getTime()) {
                        updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                    }
                    schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
                }
            }
        }
        if (BigDecimal.ZERO.compareTo(incrementOutHour) < 0) {
            needUpdate = Boolean.TRUE;
            BigDecimal nowOutHour = Objects.isNull(dailyDto.getOutHour()) ? BigDecimal.ZERO : dailyDto.getOutHour();
            updateDailyDto.setOutHour(nowOutHour.add(incrementOutHour));
        }
        // 出差时长
        List<SchedulingApplyTripRecordDto> tripRecordDtos = schedulingApplyTripRecordService.queryList(params);
        if (CollectionUtils.isNotEmpty(tripRecordDtos)) {
            List<SchedulingAttendanceDailyApplyDto> collect = tripRecordDtos.parallelStream().map(o -> {
                SchedulingAttendanceDailyApplyDto newlyDailyApplyDto = new SchedulingAttendanceDailyApplyDto();
                newlyDailyApplyDto.setApplyId(o.getId());
                newlyDailyApplyDto.setDailyId(dailyDto.getId());
                newlyDailyApplyDto.setApplyType(SchedulingApplyConstants.ApplyTypeEnum.TRIP.getType());
                newlyDailyApplyDto.setPersonId(dailyDto.getPersonId());
                return newlyDailyApplyDto;
            }).collect(Collectors.toList());
            // 绑考勤和外出申请的关系
            schedulingAttendanceDailyApplyService.saveBatch(collect);
        }
        // 找到交集部分，计算真正起效的请假时长
        BigDecimal incrementTripHour = BigDecimal.ZERO;
        for (SchedulingApplyTripRecordDto tripRecordDto : tripRecordDtos) {
            for (SchedulingAttendanceDailyTimeDto time : dailyDto.getTimes()) {
                if (tripRecordDto.getEndTime().getTime() > time.getWorkTime().getTime()
                        && tripRecordDto.getStartTime().getTime() < time.getClosingTime().getTime()) {
                    Date intersectionStartTime = new Date(Math.max(tripRecordDto.getStartTime().getTime(), time.getWorkTime().getTime()));
                    Date intersectionEndTime = new Date(Math.min(tripRecordDto.getEndTime().getTime(), time.getClosingTime().getTime()));
                    if (intersectionEndTime.getTime() - intersectionStartTime.getTime() > CAL_TINE_ACCURACY_MILLISECOND) {
                        // 存在交集，并且交集范围大于记录的最小精度
                        BigDecimal increment = new BigDecimal(String.valueOf(intersectionEndTime.getTime() - intersectionStartTime.getTime()))
                                .divide(new BigDecimal(String.valueOf(CAL_TINE_ACCURACY_MILLISECOND * 100)), 2, RoundingMode.HALF_UP);
                        incrementTripHour = incrementTripHour.add(increment);
                    }
                    // 请假造成的影响，更新打卡时间段状态
                    SchedulingAttendanceDailyTimeDto updateTimeDto = new SchedulingAttendanceDailyTimeDto();
                    updateTimeDto.setId(time.getId());
                    if (tripRecordDto.getEndTime().getTime() >= time.getClosingTime().getTime()) {
                        updateTimeDto.setCheckClosingStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                    }
                    if (tripRecordDto.getStartTime().getTime() <= time.getWorkTime().getTime()) {
                        updateTimeDto.setCheckWorkStatus(SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus());
                    }
                    schedulingAttendanceDailyTimeService.updateById(updateTimeDto);
                }
            }
        }
        if (BigDecimal.ZERO.compareTo(incrementTripHour) < 0) {
            needUpdate = Boolean.TRUE;
            BigDecimal nowTripHour = Objects.isNull(dailyDto.getTripHour()) ? BigDecimal.ZERO : dailyDto.getTripHour();
            updateDailyDto.setTripHour(nowTripHour.add(incrementTripHour));
        }

        // 全部申请找完了，检查下时间段状态，可能考勤产生的时候，就可以标记考勤正常了
        if (SchedulingAttendanceDailyConstants.CheckStatusEnum.ABNORMAL.getStatus().equals(dailyDto.getCheckStatus())) {
            List<SchedulingAttendanceDailyTimeDto> dailyAllTimes = schedulingAttendanceDailyTimeService.selectByDailyId(dailyDto.getId());
            boolean b = dailyAllTimes.parallelStream()
                    .anyMatch(o -> !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckWorkStatus())
                            || !SchedulingAttendanceDailyConstants.TimeCheckStatusEnum.NORMAL.getStatus().equals(o.getCheckClosingStatus()));
            if (!b) {
                needUpdate = Boolean.TRUE;
                updateDailyDto.setCheckStatus(SchedulingAttendanceDailyConstants.CheckStatusEnum.NORMAL.getStatus());
            }
        }

        if (Boolean.TRUE.equals(needUpdate)) {
            updateById(updateDailyDto);
        }

    }

    private Date realDate(String time, Date markDate, Byte forward) {
        String format = DateUtils.format(markDate, DateUtils.FormatType.SIMPLE);
        Date timeDate = DateUtils.parse(String.format("%s %s", format, time), DateUtils.FormatType.COMMON);
        assert timeDate != null;
        if (SystemConstants.ENABLE.equals(forward)) {
            return timeDate.getTime() > markDate.getTime() ? timeDate : DateUtil.offsetDay(timeDate, 1);
        } else {
            return timeDate.getTime() < markDate.getTime() ? timeDate : DateUtil.offsetDay(timeDate, -1);
        }
    }

    @Override
    public SchedulingAttendanceDailyDto selectOne(Long personId, String dateStr) {
        SchedulingAttendanceDailyEntity schedulingAttendanceDailyEntity = schedulingAttendanceDailyDao.selectOne(new LambdaQueryWrapper<SchedulingAttendanceDailyEntity>()
                .eq(SchedulingAttendanceDailyEntity::getPersonId, personId)
                .eq(SchedulingAttendanceDailyEntity::getDate, dateStr)
                .last(SqlConstants.MYSQL_LIMIT_ONE));
        SchedulingAttendanceDailyDto attendanceDailyDto = CopyUtils.copyObj(schedulingAttendanceDailyEntity, SchedulingAttendanceDailyDto.class);
        return this.paddingField(attendanceDailyDto);
    }

    @Override
    public SchedulingAttendanceDailyDto selectCacheById(Long id) {
        return CacheKit.cacheToRedis(
                () -> selectById(id),
                SCHEDULING_ATTENDANCE_DAILY_CACHE_BY_ID_FUNC.apply(id),
                SystemConstants.NOT_NULL_CACHE_EXPIRE_SECONDS
        );
    }

    @Override
    public SchedulingAttendanceDailyDto selectById(Long id) {
        SchedulingAttendanceDailyEntity entity = schedulingAttendanceDailyDao.selectById(id);
        return paddingField(CopyUtils.copyObj(entity, SchedulingAttendanceDailyDto.class));
    }

    @Override
    public List<SchedulingAttendanceDailyDto> queryList(Map<String, Object> params) {
        List<SchedulingAttendanceDailyEntity> list = schedulingAttendanceDailyDao.queryList(params);
        List<SchedulingAttendanceDailyDto> result = CopyUtils.copyList(list, SchedulingAttendanceDailyDto.class);
        result.forEach(this::paddingField);
        return result;
    }

    @Override
    public List<SchedulingAttendanceDailyDto> queryListWithoutPadding(Map<String, Object> params) {
        List<SchedulingAttendanceDailyEntity> list = schedulingAttendanceDailyDao.queryList(params);
        return CopyUtils.copyList(list, SchedulingAttendanceDailyDto.class);
    }

    /**
     * 此方法慎用 禁止填充List一类 比如一个人有多个地址这里不允许填充
     * 填充的原则是: 1:被填充对象不经常变换 2:根据id可以走缓存 3数据足够简单 比如设备类型 所属园区等
     *
     * @param dto
     * @return
     */
    private SchedulingAttendanceDailyDto paddingField(SchedulingAttendanceDailyDto dto) {
        if (dto != null) {
            List<SchedulingAttendanceDailyTimeDto> times = schedulingAttendanceDailyTimeService.selectByDailyId(dto.getId());
            dto.setTimes(times);

            DeptPersonDto deptPersonDto = deptPersonService.selectCacheById(dto.getPersonId());
            dto.setDeptPerson(deptPersonDto);

            SchedulingAttendanceGroupDto schedulingAttendanceGroupDto = schedulingAttendanceGroupService.selectCacheById(dto.getSchedulingAttendanceGroupId());
            dto.setSchedulingAttendanceGroup(schedulingAttendanceGroupDto);

            VillageDto villageDto = villageService.selectCacheById(dto.getVillageId());
            dto.setVillage(villageDto);

            List<SchedulingAttendanceDailyApplyDto> applyDtos = schedulingAttendanceDailyApplyService.selectCacheByDailyId(dto.getId());
            dto.setApplies(applyDtos);
        }
        return dto;
    }

    @Override
    public int queryTotal(Map<String, Object> params) {
        return schedulingAttendanceDailyDao.queryTotal(params);
    }

    @Override
    public Long save(SchedulingAttendanceDailyDto dto) {
        SchedulingAttendanceDailyEntity entity = CopyUtils.copyObj(dto, SchedulingAttendanceDailyEntity.class);
        entity.setId(Snowflake.nextId());
        schedulingAttendanceDailyDao.insert(entity);
        return entity.getId();
    }

    @Override
    public void updateById(SchedulingAttendanceDailyDto dto) {
        SchedulingAttendanceDailyEntity entity = CopyUtils.copyObj(dto, SchedulingAttendanceDailyEntity.class);

        schedulingAttendanceDailyDao.updateById(entity);

        CLEAR_CACHE_FUNC.accept(dto);
    }

    @Override
    public void updateAllColumnById(SchedulingAttendanceDailyDto dto) {
        SchedulingAttendanceDailyEntity entity = CopyUtils.copyObj(dto, SchedulingAttendanceDailyEntity.class);

        MyBatisPlusUtils.updateAllColumnById(entity, schedulingAttendanceDailyDao);

        CLEAR_CACHE_FUNC.accept(dto);
    }

    @Override
    public void deleteById(Long id) {

        CLEAR_CACHE_FUNC.accept(selectById(id));

        schedulingAttendanceDailyDao.deleteById(id);

        schedulingAttendanceDailyTimeService.deleteByDailyId(id);

        schedulingAttendanceDailyApplyService.deleteByDailyId(id);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(Long[] ids) {
        for (Long id : ids) {
            deleteById(id);
        }
    }
}
